package net.protocol.diameter.message;

import net.protocol.common.coding.ByteMe;
import net.protocol.common.util.Bits;

import java.nio.ByteBuffer;

/**
 * <h3>Diameter Header</h3>
 * <p/>
 * <p/>
 * A summary of the Diameter header format is shown below. The fields are
 * transmitted in network byte order.
 * <p/>
 * <pre>
 *     0                   1                   2                   3
 *     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *    |    Version    |                 Message Length                |
 *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *    | command flags |                  Command-Code                 |
 *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *    |                         Application-ID                        |
 *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *    |                      Hop-by-Hop Identifier                    |
 *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *    |                      End-to-End Identifier                    |
 *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 * Version
 *       This Version field MUST be set to 1 to indicate Diameter Version
 *       1.
 *
 * Message Length
 *       The Message Length field is three octets and indicates the length
 *       of the Diameter message including the header fields.
 *
 * Command Flags
 *       The Command Flags field is eight bits.  The following bits are
 *       assigned:
 *
 *        0 1 2 3 4 5 6 7
 *       +-+-+-+-+-+-+-+-+
 *       |R P E T r r r r|
 *       +-+-+-+-+-+-+-+-+
 *
 *       R(equest)   - If set, the message is a request.  If cleared, the
 *                     message is an answer.
 *       P(roxiable) - If set, the message MAY be proxied, relayed or
 *                     redirected.  If cleared, the message MUST be
 *                     locally processed.
 *       E(rror)     - If set, the message contains a protocol error,
 *                     and the message will not conform to the ABNF
 *                     described for this command.  Messages with the 'E'
 *                                         bit set are commonly referred to as error
 *                     messages.  This bit MUST NOT be set in request
 *                     messages.  See Section 7.2.
 *       T(Potentially re-transmitted message)
 *                   - This flag is set after a link failover procedure,
 *                     to aid the removal of duplicate requests.  It is
 *                     set when resending requests not yet acknowledged,
 *                     as an indication of a possible duplicate due to a
 *                     link failure.  This bit MUST be cleared when
 *                     sending a request for the first time, otherwise
 *                     the sender MUST set this flag.  Diameter agents
 *                     only need to be concerned about the number of
 *                     requests they send based on a single received
 *                     request; retransmissions by other entities need
 *                     not be tracked.  Diameter agents that receive a
 *                     request with the T flag set, MUST keep the T flag
 *                     set in the forwarded request.  This flag MUST NOT
 *                     be set if an error answer message (e.g., a
 *                     protocol error) has been received for the earlier
 *                     message.  It can be set only in cases where no
 *                     answer has been received from the server for a
 *                     request and the request is sent again.  This flag
 *                     MUST NOT be set in answer messages.
 *
 *       r(eserved)  - these flag bits are reserved for future use, and
 *                     MUST be set to zero, and ignored by the receiver.
 *    Command-Code
 *       The Command-Code field is three octets, and is used in order to
 *       communicate the command associated with the message.  The 24-bit
 *       address space is managed by IANA (see Section 11.2.1).
 *
 *       Command-Code values 16,777,214 and 16,777,215 (hexadecimal values
 *       FFFFFE -FFFFFF) are reserved for experimental use (See Section
 *       11.3).
 *
 *    Application-ID
 *       Application-ID is four octets and is used to identify to which
 *       application the message is applicable for.  The application can be
 *       an authentication application, an accounting application or a
 *       vendor specific application.  See Section 11.3 for the possible
 *       values that the application-id may use.
 *
 *       The application-id in the header MUST be the same as what is
 *       contained in any relevant AVPs contained in the message.
 *    Hop-by-Hop Identifier
 *       The Hop-by-Hop Identifier is an unsigned 32-bit integer field (in
 *       network byte order) and aids in matching requests and replies.
 *       The sender MUST ensure that the Hop-by-Hop identifier in a request
 *       is unique on a given connection at any given time, and MAY attempt
 *       to ensure that the number is unique across reboots.  The sender of
 *       an Answer message MUST ensure that the Hop-by-Hop Identifier field
 *       contains the same value that was found in the corresponding
 *       request.  The Hop-by-Hop identifier is normally a monotonically
 *       increasing number, whose start value was randomly generated.  An
 *       answer message that is received with an unknown Hop-by-Hop
 *       Identifier MUST be discarded.
 *
 *    End-to-End Identifier
 *       The End-to-End Identifier is an unsigned 32-bit integer field (in
 *       network byte order) and is used to detect duplicate messages.
 *       Upon reboot implementations MAY set the high order 12 bits to
 *       contain the low order 12 bits of current time, and the low order
 *       20 bits to a random value.  Senders of request messages MUST
 *       insert a unique identifier on each message.  The identifier MUST
 *       remain locally unique for a period of at least 4 minutes, even
 *       across reboots.  The originator of an Answer message MUST ensure
 *       that the End-to-End Identifier field contains the same value that
 *       was found in the corresponding request.  The End-to-End Identifier
 *       MUST NOT be modified by Diameter agents of any kind.  The
 *       combination of the Origin-Host (see Section 6.3) and this field is
 *       used to detect duplicates.  Duplicate requests SHOULD cause the
 *       same answer to be transmitted (modulo the hop-by-hop Identifier
 *       field and any routing AVPs that may be present), and MUST NOT
 *       affect any state that was set when the original request was
 *       processed.  Duplicate answer messages that are to be locally
 *       consumed (see Section 6.2) SHOULD be silently discarded.
 * </pre>
 *
 * @author jinhongw@gmail.com
 */
class DiameterHeader implements ByteMe {
    private final static int REQ_MASK = 0x80;
    private final static int PXY_MASK = 0x40;
    private final static int ERR_MASK = 0x20;
    private final static int RET_MASK = 0x10;

    private final byte version;
    private int messageLength;

    private byte commandFlags;
    private final int commandCode;

    private int applicationID;
 	// an unsigned 32-bit integer
    private long hopByHopIdentifier; 
    // an unsigned 32-bit integer
    private long endToEndIdentifier; 

    /**
     * @param version     Version
     * @param commandCode Command Code
     */
    public DiameterHeader(byte version, int commandCode) {
        this.version = version;
        this.commandCode = commandCode;
    }

    /**
     * Creates a new <code>DiameterHeader</code> with the given source ByteBuffer
     * 
     * @param src The buffer from which bytes are to be retrieved
     */
    public DiameterHeader(ByteBuffer src) {
        int vml = src.getInt();
        this.version = (byte) (vml >> 24);
        this.messageLength = ((vml << 8) >>> 8);

        int cc = src.getInt();
        this.commandFlags = (byte) (cc >> 24);
        this.commandCode = ((cc << 8) >>> 8);

        this.applicationID = src.getInt();
        this.hopByHopIdentifier = Bits.getU32(src);
        this.endToEndIdentifier = Bits.getU32(src);
    }

    @Override
    public ByteBuffer byteMe(ByteBuffer dst) {
        int vml = (this.version << 24) + this.messageLength;
        dst.putInt(vml);

        int cc = (this.commandFlags << 24) + this.commandCode;
        dst.putInt(cc);

        dst.putInt(applicationID);
        
        Bits.putU32(dst, hopByHopIdentifier);
        Bits.putU32(dst, endToEndIdentifier);
        
        return dst;
    }

    public boolean isRequest() {
        return (commandFlags & REQ_MASK) != 0;
    }

    public boolean isProxiable() {
        return (commandFlags & PXY_MASK) != 0;
    }

    public boolean isError() {
        return (commandFlags & ERR_MASK) != 0;
    }

    public boolean isReTransmitted() {
        return (commandFlags & RET_MASK) != 0;
    }

    public void setRequest(boolean value) {
        if (value) {
            commandFlags |= REQ_MASK;
        } else {
            commandFlags &= ~REQ_MASK;
        }
    }

    public void setProxiable(boolean value) {
        if (value) {
            commandFlags |= PXY_MASK;
        } else {
            commandFlags &= ~PXY_MASK;
        }
    }

    public void setError(boolean value) {
        if (value) {
            commandFlags |= ERR_MASK;
        } else {
            commandFlags &= ~ERR_MASK;
        }
    }

    public void setReTransmitted(boolean value) {
        if (value) {
            commandFlags |= RET_MASK;
        } else {
            commandFlags &= ~RET_MASK;
        }
    }

    public byte getVersion() {
        return version;
    }

    public int getMessageLength() {
        return messageLength;
    }

    public void setMessageLength(int messageLength) {
        this.messageLength = messageLength;
    }

    public byte getCommandFlags() {
        return commandFlags;
    }

    public int getCommandCode() {
        return commandCode;
    }

    public int getApplicationID() {
        return applicationID;
    }

    public void setApplicationID(int applicationID) {
        this.applicationID = applicationID;
    }

    public long getHopByHopIdentifier() {
        return hopByHopIdentifier;
    }

    public void setHopByHopIdentifier(long hopByHopIdentifier) {
        this.hopByHopIdentifier = hopByHopIdentifier;
    }

    public long getEndToEndIdentifier() {
        return endToEndIdentifier;
    }

    public void setEndToEndIdentifier(long endToEndIdentifier) {
        this.endToEndIdentifier = endToEndIdentifier;
    }

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		DiameterHeader other = (DiameterHeader) obj;
		if (applicationID != other.applicationID)
			return false;
		if (commandCode != other.commandCode)
			return false;
		if (commandFlags != other.commandFlags)
			return false;
		if (endToEndIdentifier != other.endToEndIdentifier)
			return false;
		if (hopByHopIdentifier != other.hopByHopIdentifier)
			return false;
		if (messageLength != other.messageLength)
			return false;
		if (version != other.version)
			return false;
		return true;
	}
	
    @Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + applicationID;
		result = prime * result + commandCode;
		result = prime * result + commandFlags;
		result = prime * result
				+ (int) (endToEndIdentifier ^ (endToEndIdentifier >>> 32));
		result = prime * result
				+ (int) (hopByHopIdentifier ^ (hopByHopIdentifier >>> 32));
		result = prime * result + messageLength;
		result = prime * result + version;
		return result;
	}

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(getClass().getSimpleName()).append(" [");
        buf.append("Version=").append(version).append(", ");
        buf.append("Message Length=").append(messageLength).append(", ");
        buf.append("Command-Flags=").append(commandFlags).append(", ");
        buf.append("Command-Code=").append(commandCode).append(", ");

        buf.append("Application-ID=").append(applicationID).append(", ");
        buf.append("Hop-by-Hop Identifier=").append(hopByHopIdentifier).append(", ");
        buf.append("End-to-End Identifier=").append(endToEndIdentifier);
        buf.append("]");
        return buf.toString();
    }
}
